package com.busfm.media;

import android.media.MediaPlayer;
import android.media.MediaPlayer.OnBufferingUpdateListener;
import android.media.MediaPlayer.OnCompletionListener;
import android.media.MediaPlayer.OnErrorListener;
import android.media.MediaPlayer.OnPreparedListener;
import android.os.Handler;
import android.util.Log;
import android.widget.Toast;

import com.busfm.BusfmApplication;
import com.busfm.R;
import com.busfm.activity.LoginActivity;
import com.busfm.model.PlayList;
import com.busfm.model.SongEntity;
import com.busfm.util.Constants;
import com.busfm.util.LogUtil;
import com.busfm.util.Utilities;

public class PlayControllerImpl implements PlayController {
    /**
     * Time frame - used for counting number of fails within that time
     */
    private static final long FAIL_TIME_FRAME = 1000;

    /**
     * Acceptable number of fails within FAIL_TIME_FRAME
     */
    private static final int ACCEPTABLE_FAIL_NUMBER = 2;

    /**
     * Beginning of last FAIL_TIME_FRAME
     */
    private long mLastFailTime;
    private MediaPlayerWrapper mediaPlayer;
    /**
     * Number of times failed within FAIL_TIME_FRAME
     */
    private long mTimesFailed;
    private PlayList mPlayList = new PlayList();
    private PlayControllerListener mPlayControllerListener;
    /**
     * Handler to the context thread
     */
    private Handler mHandler;

    public PlayControllerImpl() {
        mLastFailTime = 0;
        mTimesFailed = 0;
        mHandler = new Handler();
    }

    /**
     * Runnable periodically querying Media Player
     * about the current position of the track and
     * notifying the listener
     */
    private Runnable mUpdateTimeTask = new Runnable() {
        public void run() {

            if (mPlayControllerListener != null) {
                // TODO use getCurrentPosition less frequently (usage of currentTimeMillis or uptimeMillis)
                if (mediaPlayer != null)
                    mPlayControllerListener.onTrackProgress(mediaPlayer.getCurrentPosition() / 1000);
                mHandler.postDelayed(this, 1000);
            }
        }
    };

    private class MediaPlayerWrapper extends MediaPlayer {
        public SongEntity songEntity;//record current playEntity
        public boolean preparing = false;
        public boolean playAfterPrepare = false;
    }

    @Override
    public void setPlaylist(PlayList playlist) {
        if (null != playlist) {
            mPlayList = playlist;
            return;
        }
        mPlayList = null;
    }

    @Override
    public PlayList getPlaylist() {
        return mPlayList;
    }

    @Override
    public void play() {
        if (mPlayControllerListener.onTrackStart() == false) {
            return;
        }

        if (null != mPlayList) {
            if (null == mediaPlayer) {
                mediaPlayer = build(mPlayList.getSelectedEntity());
            }

            // PlayNext Or Other Songs
            if (mediaPlayer != null && !mediaPlayer.songEntity.equals(mPlayList.getSelectedEntity())) {
                onClean(); // this will do the cleanup job        
                mediaPlayer = build(mPlayList.getSelectedEntity());
            }

            if (null != mediaPlayer && !mediaPlayer.preparing) {
                if (!mediaPlayer.isPlaying()) {
                    Log.i(Constants.TAG, "Player [playing] " + mPlayList.getSelectedEntity().title);
                    mediaPlayer.start();
                    BusfmApplication.getInstance().setDuration(mediaPlayer.getDuration() / 1000);
                    LogUtil.i(Constants.TAG, "Duration :" + mediaPlayer.getDuration());
                    // starting timer
                    mHandler.removeCallbacks(mUpdateTimeTask);
                    mHandler.postDelayed(mUpdateTimeTask, 1000);
                }
            } else {
                mediaPlayer.playAfterPrepare = true;
            }
        }
    }

    @Override
    public boolean isPlaying() {
        if (null == mediaPlayer) {
            return false;
        }

        if (mediaPlayer.preparing)
            return false;

        return mediaPlayer.isPlaying();
    }

    @Override
    public void stop() {
        onClean();
    }

    @Override
    public void pause() {
        // still preparing
        if (null != mediaPlayer) {
            if (mediaPlayer.preparing) {
                mediaPlayer.playAfterPrepare = false;
                return;
            }

            // check if we play, then pause
            if (mediaPlayer.isPlaying()) {
                mediaPlayer.pause();
                if (mPlayControllerListener != null)
                    mPlayControllerListener.onTrackPause();
                return;
            }
        }
    }

    @Override
    public void next() {
        mPlayList.selectNext();
        play();
    }

    @Override
    public void prev() {
        mPlayList.selectPrev();
        play();
    }

    @Override
    public void skipTo(int index) {
        mPlayList.select(index);
        play();
    }

    @Override
    public void setListener(PlayControllerListener playerEngineListener) {
        mPlayControllerListener = playerEngineListener;
    }

    /**
     * clean data of player
     */
    public void onClean() {
        if (mediaPlayer != null) {
            try {
                mediaPlayer.pause();
                mediaPlayer.stop();
            } catch (Exception e) {
                LogUtil.e(Constants.TAG, e.getMessage());
            } finally {
                mediaPlayer.release();
                mediaPlayer = null;
            }
        }
    }

    private MediaPlayerWrapper build(SongEntity songEntity) {
        // failed
        if (null != songEntity && Utilities.isEmpty(songEntity.location)) {
            if (mPlayControllerListener != null) {
                mPlayControllerListener.onTrackStreamError();
                mPlayControllerListener.onTrackChanged(mPlayList.getSelectedEntity());
            }
            stop();
            return null;
        }
        try {
            mediaPlayer = new MediaPlayerWrapper();
            mediaPlayer.songEntity = songEntity;//current song
            mediaPlayer.setDataSource(songEntity.location);
            //            mediaPlayer.setAudioStreamType(AudioManager.STREAM_MUSIC);
            LogUtil.i(Constants.TAG, "Now Playing :" + songEntity.location);
            mediaPlayer.setOnCompletionListener(new OnCompletionListener() {

                @Override
                public void onCompletion(MediaPlayer mp) {
                    next();
                }

            });
            mediaPlayer.setOnBufferingUpdateListener(new OnBufferingUpdateListener() {

                @Override
                public void onBufferingUpdate(MediaPlayer mp, int percent) {
                    if (mPlayControllerListener != null) {
                        mPlayControllerListener.onTrackBuffering(percent);
                    }
                }
            });
            mediaPlayer.setOnPreparedListener(new OnPreparedListener() {

                @Override
                public void onPrepared(MediaPlayer mp) {
                    mediaPlayer.preparing = false;
                    if (mediaPlayer != null && mediaPlayer.songEntity == mPlayList.getSelectedEntity()
                            && mediaPlayer.playAfterPrepare) {
                        mediaPlayer.playAfterPrepare = false;
                        play();
                    }
                }
            });
            mediaPlayer.setOnErrorListener(new OnErrorListener() {

                @Override
                public boolean onError(MediaPlayer mp, int what, int extra) {
                    LogUtil.e(Constants.TAG, "Error : " + what);
                    if (what == MediaPlayer.MEDIA_ERROR_UNKNOWN) {
                        // we probably lack network
                        if (mPlayControllerListener != null) {
                            mPlayControllerListener.onTrackStreamError();
                        }
                        stop();
                        return true;
                    }
                    // not sure what error code -1 exactly stands for but it
                    // causes player to start to jump songs
                    // if there are more than 5 jumps without playback during 1
                    // second then we abort
                    // further playback
                    if (what == -1) {
                        long failTime = System.currentTimeMillis();
                        if (failTime - mLastFailTime > FAIL_TIME_FRAME) {
                            // outside time frame
                            mTimesFailed = 1;
                            mLastFailTime = failTime;
                            Log.w(Constants.TAG, "PlayerEngineImpl " + mTimesFailed + " fail within FAIL_TIME_FRAME");
                        } else {
                            // inside time frame
                            mTimesFailed++;
                            if (mTimesFailed > ACCEPTABLE_FAIL_NUMBER) {
                                Log.w(Constants.TAG, "PlayerEngineImpl too many fails, aborting playback");
                                if (mPlayControllerListener != null) {
                                    mPlayControllerListener.onTrackStreamError();
                                }
                                stop();
                                return true;
                            }
                        }
                    }
                    return false;
                }
            });

            // start preparing
            //            Log.i(Constants.TAG, "Player [buffering] "+mediaPlayer.playlistEntry.getTrack().getName());
            mediaPlayer.preparing = true;
            mediaPlayer.prepareAsync();

            // this is a new track, so notify the listener
            if (mPlayControllerListener != null) {
                mPlayControllerListener.onTrackChanged(mPlayList.getSelectedEntity());
            }
            return mediaPlayer;
        } catch (Exception e) {
            LogUtil.e(Constants.TAG, e.getMessage());
        }
        return null;
    }

}
